Conversation
Contributor
DavertMik
commented
Apr 29, 2025
- list tests & suites
- list all available actions
- tool to open and close browser
- ability to control browser via actions
- tool to collect HTML / page info
- tool for screenshot
Contributor
There was a problem hiding this comment.
Pull Request Overview
This PR introduces documentation for integrating CodeceptJS with Cursor using the Model Context Protocol (MCP), outlining how to start the MCP server and utilize its available tools.
- Added a dedicated markdown file (docs/MCP.md) detailing MCP usage with Cursor
- Provided step-by-step instructions for starting the MCP server and connecting via Cursor
- Outlined available tools and included troubleshooting guidance for common issues
Files not reviewed (1)
- package.json: Language not supported
|
|
||
| The server provides these tools: | ||
|
|
||
| - `list-tests`: List all availble tests |
There was a problem hiding this comment.
Typo detected in the word 'availble'; please update it to 'available'.
Suggested change
| - `list-tests`: List all availble tests | |
| - `list-tests`: List all available tests |
kobenguyent
reviewed
May 1, 2025
Collaborator
kobenguyent
left a comment
There was a problem hiding this comment.
What do you think if we do like this to keep the server.tool clean and reusable?
| @@ -0,0 +1,185 @@ | |||
| #!/usr/bin/env node | |||
Collaborator
There was a problem hiding this comment.
#!/usr/bin/env node
const { McpServer, ResourceTemplate } = require('@modelcontextprotocol/sdk/server/mcp.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
// Import specific CodeceptJS modules directly
const Codecept = require('../lib/codecept');
const container = require('../lib/container');
const { getParamsToString } = require('../lib/parser');
const { methodsOfObject } = require('../lib/utils');
const output = require('../lib/output');
const { getConfig, getTestRoot } = require('../lib/command/utils');
// Determine the project path
const projectPath = process.argv.slice(2).find(arg => !arg.includes('mcp.js')) || process.cwd();
/**
* Initializes CodeceptJS and loads tests.
* @param {string} customPath - The project path.
* @returns {object} - The initialized CodeceptJS instance and its configuration.
*/
async function initializeCodeceptJS(customPath) {
output.print = () => {}; // Disable default output
const testsPath = getTestRoot(customPath);
const config = getConfig(customPath);
const codecept = new Codecept(config, {});
await codecept.init(testsPath);
codecept.loadTests();
return { codecept, config };
}
/**
* Extracts test information from the loaded CodeceptJS instance.
* @param {object} codecept - The initialized CodeceptJS instance.
* @returns {Array<object>} - An array of test objects.
*/
function extractTestInfo(codecept) {
const mocha = container.mocha();
mocha.files = codecept.testFiles;
mocha.loadFiles();
const tests = [];
for (const suite of mocha.suite.suites) {
for (const test of suite.tests) {
tests.push({
title: test.title,
fullTitle: test.fullTitle(),
body: test.body ? test.body.toString() : '',
file: suite.file,
suiteName: suite.title,
meta: test.meta,
tags: test.tags,
});
}
}
return tests;
}
/**
* Formats test information into a readable text block.
* @param {Array<object>} tests - An array of test objects.
* @returns {object} - An MCP content object containing the formatted text.
*/
function formatTestsAsText(tests) {
const formattedText = tests
.map(test => [
`Test: ${test.fullTitle}`,
`File: ${test.file}`,
`Suite: ${test.suiteName}`,
test.tags && test.tags.length ? `Tags: ${test.tags.join(', ')}` : '',
'',
'Body:',
test.body,
'---',
]
.filter(Boolean)
.join('\n'))
.join('\n\n');
return { content: [{ type: 'text', text: formattedText }] };
}
/**
* Extracts suite information from the loaded CodeceptJS instance.
* @param {object} codecept - The initialized CodeceptJS instance.
* @returns {Array<object>} - An array of suite objects.
*/
function extractSuiteInfo(codecept) {
const mocha = container.mocha();
mocha.files = codecept.testFiles;
mocha.loadFiles();
const suites = [];
for (const suite of mocha.suite.suites) {
suites.push({
title: suite.title,
file: suite.file,
testCount: suite.tests.length,
tests: suite.tests.map(test => ({
title: test.title,
fullTitle: test.fullTitle(),
})),
});
}
return suites;
}
/**
* Formats suite information into a readable text block.
* @param {Array<object>} suites - An array of suite objects.
* @returns {object} - An MCP content object containing the formatted text.
*/
function formatSuitesAsText(suites) {
const formattedText = suites
.map(suite => {
const testList = suite.tests.map(test => ` - ${test.title}`).join('\n');
return [`Suite: ${suite.title}`, `File: ${suite.file}`, `Tests (${suite.testCount}):`, testList, '---'].join('\n');
})
.join('\n\n');
return { content: [{ type: 'text', text: formattedText }] };
}
/**
* Extracts action information from CodeceptJS helpers and support objects.
* @returns {Array<object>} - An array of action objects.
*/
function extractActionInfo() {
const helpers = container.helpers();
const supportI = container.support('I');
const actions = [];
// Get actions from helpers
for (const name in helpers) {
const helper = helpers[name];
methodsOfObject(helper).forEach(action => {
const params = getParamsToString(helper[action]);
actions.push({
name: action,
source: name,
params,
type: 'helper',
});
});
}
// Get actions from I
for (const name in supportI) {
if (actions.some(a => a.name === name)) {
continue;
}
const actor = supportI[name];
const params = getParamsToString(actor);
actions.push({
name,
source: 'I',
params,
type: 'support',
});
}
return actions;
}
/**
* Formats action information into a readable text block.
* @param {Array<object>} actions - An array of action objects.
* @returns {object} - An MCP content object containing the formatted text.
*/
function formatActionsAsText(actions) {
const helperActions = actions
.filter(a => a.type === 'helper')
.sort((a, b) => (a.source === b.source ? a.name.localeCompare(b.name) : a.source.localeCompare(b.source)));
const supportActions = actions.filter(a => a.type === 'support').sort((a, b) => a.name.localeCompare(b.name));
const formattedText = [
'# Helper Actions',
...helperActions.map(a => `${a.source}.${a.name}(${a.params})`),
'',
'# Support Actions',
...supportActions.map(a => `I.${a.name}(${a.params})`),
].join('\n');
return { content: [{ type: 'text', text: formattedText }] };
}
/**
* Starts the MCP Server.
*/
async function startServer() {
const { codecept } = await initializeCodeceptJS(projectPath);
const server = new McpServer({
name: 'CodeceptJS',
version: '1.0.0',
url: 'https://codecept.io',
description: 'CodeceptJS Model Context Protocol Server',
});
server.tool('list-tests', {}, async () => {
const tests = extractTestInfo(codecept);
return formatTestsAsText(tests);
});
server.tool('list-suites', {}, async () => {
const suites = extractSuiteInfo(codecept);
return formatSuitesAsText(suites);
});
server.tool('list-actions', {}, async () => {
const actions = extractActionInfo();
return formatActionsAsText(actions);
});
const transport = new StdioServerTransport();
await server.connect(transport);
}
// Start the server
startServer().catch(err => {
console.error('Error starting MCP server:', err);
process.exit(1);
});
kobenguyent
reviewed
May 1, 2025
| // Setup MCP server | ||
| const server = new McpServer({ | ||
| name: 'CodeceptJS', | ||
| version: '1.0.0', |
Collaborator
There was a problem hiding this comment.
Shall we get the current version of codeceptjs?
Contributor
|
@kobenguyent @DavertMik Are there any plans to bring this feature? |
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.